/*
 * MedianOp.java
 */

package com.photoexpert;

import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ColorConvertOp;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;

/**
 *
 * @author PANYIXIN
 */
public class MedianOp implements BufferedImageOp {

    public MedianOp(int size, RenderingHints hints) {
        _hints = hints;
        _size = size;
    }

    @Override
    public BufferedImage filter(BufferedImage src, BufferedImage dest) {
        if (src == null)
            throw new NullPointerException("src image is null");
        if (src == dest)
            throw new IllegalArgumentException("MedianOp does not support inline filter");
        boolean needToConvert = false;
        BufferedImage originalDest = dest;
        if (dest == null) {
            dest = createCompatibleDestImage(src, null);
            originalDest = dest;
        }
        else {
            if (src.getColorModel().getColorSpace().getType() !=
                dest.getColorModel().getColorSpace().getType())
            {
                needToConvert = true;
                dest = createCompatibleDestImage(src, null);
            }
            else if (dest.getColorModel() instanceof IndexColorModel) {
                dest = createCompatibleDestImage(src, null);
            }
        }
        int width = src.getWidth();
        int height = src.getHeight();
        for(int i = 0; i < width; ++i)
            for(int j = 0; j < height; ++j)
            {
                int rgbs[] = new int[(_size * 2 + 1) * (_size * 2 + 1)];
                int count = 0;
                for(int m = i - _size; m < i + _size; ++m)
                    for(int n = j - _size; n < j + _size; ++n)
                    {
                        if(m >= 0 && n > 0 && m < width && n < height) {
                            int rgb = src.getRGB(m, n);
                            int k = count - 1;
                            while(k >= 0) {
                                if(rgbs[k] > rgb)
                                    rgbs[k + 1] = rgbs[k];
                                else
                                    break;
                                --k;
                            }
                            rgbs[k + 1] = rgb;
                            count++;
                        }
                    }
                dest.setRGB(i, j, rgbs[count / 2]);
            }
        if (needToConvert) {
            ColorConvertOp ccop = new ColorConvertOp(_hints);
            ccop.filter(dest, originalDest);
        }
        else if (originalDest != dest) {
            Graphics2D g = originalDest.createGraphics();
            try {
                g.drawImage(dest, 0, 0, null);
    	    } finally {
    	        g.dispose();
    	    }
        }
        return originalDest;
    }

    @Override
    public Rectangle2D getBounds2D(BufferedImage src) {
        return src.getRaster().getBounds();
    }

    @Override
    public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM) {
        BufferedImage image;
        if (destCM == null) {
            ColorModel cm = src.getColorModel();
            image = new BufferedImage(cm,
                src.getRaster().createCompatibleWritableRaster(),
                cm.isAlphaPremultiplied(),
                null);
        }
        else {
            int w = src.getWidth();
            int h = src.getHeight();
            image = new BufferedImage (destCM,
                destCM.createCompatibleWritableRaster(w, h),
                destCM.isAlphaPremultiplied(), null);
        }

        return image;
    }

    @Override
    public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
        if (dstPt == null) {
            dstPt = new Point2D.Float();
        }
        dstPt.setLocation(srcPt.getX(), srcPt.getY());
        return dstPt;
    }

    @Override
    public RenderingHints getRenderingHints() {
        return _hints;
    }

    private RenderingHints  _hints;
    private int             _size;
}
